using System;
using System.Collections.Generic;
using System.Text;
using SystemFunctionLib.Apis;

namespace SystemFunctionLib.Multimedia
{
    public class AudioMixer
    {
        public static double? Volume
        {
            get
            {
                return GetVolume(ComponentType.DestSpeakers);
            }
            set
            {
                SetVolume(ComponentType.DestSpeakers, value ?? 0);
            }
        }

        public static bool? IsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.DestSpeakers);
            }
            set
            {
                SetIsMuted(ComponentType.DestSpeakers, value ?? false);
            }
        }

        public static double? WaveOutVolume
        {
            get
            {
                return GetVolume(ComponentType.SrcWaveOut);
            }
            set
            {
                SetVolume(ComponentType.SrcWaveOut, value ?? 0);
            }
        }

        public static bool? WaveOutIsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.SrcWaveOut);
            }
            set
            {
                SetIsMuted(ComponentType.SrcWaveOut, value ?? false);
            }
        }

        public static double? CDPlayerVolume
        {
            get
            {
                return GetVolume(ComponentType.SrcCDPlayer);
            }
            set
            {
                SetVolume(ComponentType.SrcCDPlayer, value ?? 0);
            }
        }

        public static bool? CDPlayerIsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.SrcCDPlayer);
            }
            set
            {
                SetIsMuted(ComponentType.SrcCDPlayer, value ?? false);
            }
        }

        public static double? SWSynthVolume
        {
            get
            {
                return GetVolume(ComponentType.SrcSWSynth);
            }
            set
            {
                SetVolume(ComponentType.SrcSWSynth, value ?? 0);
            }
        }

        public static bool? SWSynthIsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.SrcSWSynth);
            }
            set
            {
                SetIsMuted(ComponentType.SrcSWSynth, value ?? false);
            }
        }

        public static double? TelephoneVolume
        {
            get
            {
                return GetVolume(ComponentType.DestTelephone);
            }
            set
            {
                SetVolume(ComponentType.DestTelephone, value ?? 0);
            }
        }

        public static bool? TelephoneIsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.DestTelephone);
            }
            set
            {
                SetIsMuted(ComponentType.DestTelephone, value ?? false);
            }
        }

        public static double? PCSpeakerVolume
        {
            get
            {
                return GetVolume(ComponentType.SrcPCSpeaker);
            }
            set
            {
                SetVolume(ComponentType.SrcPCSpeaker, value ?? 0);
            }
        }

        public static bool? PCSpeakerIsMuted
        {
            get
            {
                return GetIsMuted(ComponentType.SrcPCSpeaker);
            }
            set
            {
                SetIsMuted(ComponentType.SrcPCSpeaker, value ?? false);
            }
        }

        public static double? GetVolume(ComponentType componentType)
        {
            return GetFaderControlData(componentType, ControlType.VOLUME);
        }

        public static bool SetVolume(ComponentType componentType, double newVolume)
        {
            return SetFaderControlData(componentType, ControlType.VOLUME, newVolume);
        }

        public static bool? GetIsMuted(ComponentType componentType)
        {
            return GetSwitchControlData(componentType, ControlType.MUTE);
        }

        public static bool SetIsMuted(ComponentType componentType, bool newIsMuted)
        {
            return SetSwitchControlData(componentType, ControlType.MUTE, newIsMuted);
        }

        private static bool PrepareControlDetails(
            ComponentType componentType,
            ControlType ctrlType,
            ref uint hmixer,
            ref uint hmem,
            ref MIXERCONTROL mxc,
            ref MIXERCONTROLDETAILS mxcd)
        {
            if (WinApis.mixerGetNumDevs() < 1)
            {
                return false;
            }

            MMSYSERR Rc = WinApis.mixerOpen(ref hmixer, 0, 0, 0, 0);
            if (Rc != MMSYSERR.NOERROR)
            {
                return false;
            }

            MIXERLINE mxl = new MIXERLINE();
            mxl.cbStruct = CoreApis.SizeOf(mxl);
            mxl.dwComponentType = componentType;

            Rc = WinApis.mixerGetLineInfo(hmixer, ref mxl, GetLineInfoFlag.COMPONENTTYPE);
            if (Rc != MMSYSERR.NOERROR)
            {
                return false;
            }

            mxc.cbStruct = CoreApis.SizeOf(mxc);

            MIXERLINECONTROLS mxlc = new MIXERLINECONTROLS();
            mxlc.cbStruct = CoreApis.SizeOf(mxlc);
            mxlc.dwLineID = mxl.dwLineID;
            mxlc.dwControl = (uint)ctrlType;
            mxlc.cControls = mxl.cControls;
            mxlc.cbmxctrl = CoreApis.SizeOf(mxc);

            hmem = WinApis.GlobalAlloc(64, CoreApis.SizeOf(mxc));
            mxlc.pamxctrl = WinApis.GlobalLock(hmem);

            Rc = WinApis.mixerGetLineControls(hmixer, ref mxlc, GetLineControlsFlag.ONEBYTYPE);
            if (Rc != MMSYSERR.NOERROR)
            {
                WinApis.GlobalFree(hmem);
                return false;
            }

            WinApis.RtlMoveMemory(ref mxc, mxlc.pamxctrl, CoreApis.SizeOf(mxc));
            WinApis.GlobalFree(hmem);

            mxcd.item = 0;
            mxcd.dwControlID = mxc.dwControlID;
            mxcd.cbStruct = CoreApis.SizeOf(mxcd);
            mxcd.cChannels = 1;

            return true;
        }

        /*
         * FADERS (without Equalizer)
         * 
         * Fader
         * General fade control. The range of acceptable values is 0 through 65,535.
         * 
         * Volume
         * General volume fade control. The range of acceptable values is 0 through 65,535. For 
         * information about changing this range, see the documentation for your mixer device.
         * 
         * Bass
         * Bass volume fade control. The range of acceptable values is 0 through 65,535. The limits of 
         * the bass frequency band are hardware specific. For information about band limits, see the 
         * documentation for your mixer device.
         * 
         * Treble
         * Treble volume fade control. The range of acceptable values is 0 through 65,535. The limits 
         * of the treble frequency band are hardware specific. For information about the band limits, see 
         * the documentation for your mixer device.
         * 
         */

        private static double? GetFaderControlData(
            ComponentType componentType,
            ControlType ctrlType)
        {
            double result = 0;

            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();

            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return null;
            }

            MIXERCONTROLDETAILS_UNSIGNED mxcdu = new MIXERCONTROLDETAILS_UNSIGNED();
            uint mxcd_len = CoreApis.SizeOf(mxcdu);

            hmem = WinApis.GlobalAlloc(64, mxcd_len);
            mxcd.cbDetails = mxcd_len;
            mxcd.paDetails = WinApis.GlobalLock(hmem);

            MMSYSERR Rc = WinApis.mixerGetControlDetails(hmixer, ref mxcd, GetControlDetailsFlag.VALUE);
            if (Rc != MMSYSERR.NOERROR)
            {
                WinApis.GlobalFree(hmem);
                return null;
            }

            WinApis.RtlMoveMemory(ref mxcdu, mxcd.paDetails, mxcd_len);
            WinApis.GlobalFree(hmem);

            WinApis.mixerClose(hmixer);

            result = (double)mxcdu.dwValue / (mxc.dwMaximum - mxc.dwMinimum);
            return result;
        }

        private static bool SetFaderControlData(
            ComponentType componentType,
            ControlType ctrlType,
            double value)
        {
            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();
            
            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return false;
            }

            MIXERCONTROLDETAILS_UNSIGNED mxcdu = new MIXERCONTROLDETAILS_UNSIGNED();
            hmem = WinApis.GlobalAlloc(64, CoreApis.SizeOf(mxcdu));
            mxcd.cbDetails = CoreApis.SizeOf(mxcdu);
            mxcd.paDetails = WinApis.GlobalLock(hmem);
            mxcdu.dwValue = mxc.dwMinimum + (uint)(value * (mxc.dwMaximum - mxc.dwMinimum));

            WinApis.RtlMoveMemory(mxcd.paDetails, ref mxcdu, CoreApis.SizeOf(mxcdu));

            MMSYSERR Rc = WinApis.mixerSetControlDetails(hmixer, ref mxcd, SetControlDetailsFlag.VALUE);
            WinApis.GlobalFree(hmem);

            if (Rc != MMSYSERR.NOERROR)
            {
                return false;
            }

            WinApis.mixerClose(hmixer);

            return true;
        }

        /*
         * SWITCHES
         * 
         * Boolean
         * The generic switch. It can be set to TRUE or FALSE.
         * 
         * Button
         * Set to TRUE for all buttons that the driver should handle as though they had been pressed. If 
         * the value is FALSE, no action is taken.
         * 
         * On/Off
         * An alternative switch that is represented by a graphic other than the one used for the Boolean 
         * switch. It can be set to ON or OFF.
         * 
         * Mute
         * Mutes an audio line (suppressing the data flow of the line) or allows the audio data to play. 
         * This switch is frequently used to help control the lines feeding into the mixer.
         * 
         * Mono
         * Switches between mono and stereo output for a stereo audio line. Set to OFF to play stereo data 
         * as separate channels. Set to ON to combine data from both channels into a mono audio line.
         * 
         * Loudness
         * Boosts low-volume bass for an audio line. Set to ON to boost low-volume bass. Set to OFF to set
         * volume levels to normal. The amount of boost is hardware specific. For more information, see the documentation for your mixer device.
         * 
         * Stereo Enhanced
         * Increases stereo separation. Set to ON to increase stereo separation. Set to OFF for no 
         * enhancement.
         * 
         */

        private static bool? GetSwitchControlData(
            ComponentType componentType,
            ControlType ctrlType)
        {
            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();

            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return null;
            }

            MIXERCONTROLDETAILS_BOOLEAN mxcdb = new MIXERCONTROLDETAILS_BOOLEAN();
            uint mxcd_len = CoreApis.SizeOf(mxcdb);

            hmem = WinApis.GlobalAlloc(64, mxcd_len);
            mxcd.cbDetails = mxcd_len;
            mxcd.paDetails = WinApis.GlobalLock(hmem);

            MMSYSERR Rc = WinApis.mixerGetControlDetails(hmixer, ref mxcd, GetControlDetailsFlag.VALUE);
            if (Rc != MMSYSERR.NOERROR)
            {
                WinApis.GlobalFree(hmem);
                return null;
            }

            WinApis.RtlMoveMemory(ref mxcdb, mxcd.paDetails, mxcd_len);
            WinApis.GlobalFree(hmem);

            WinApis.mixerClose(hmixer);

            return (mxcdb.fValue != 0);
        }

        private static bool SetSwitchControlData(
            ComponentType componentType,
            ControlType ctrlType,
            bool value)
        {
            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();

            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return false;
            }

            MIXERCONTROLDETAILS_BOOLEAN mxcdb = new MIXERCONTROLDETAILS_BOOLEAN();
            hmem = WinApis.GlobalAlloc(64, CoreApis.SizeOf(mxcdb));
            mxcd.cbDetails = CoreApis.SizeOf(mxcdb);
            mxcd.paDetails = WinApis.GlobalLock(hmem);
            mxcdb.fValue = (value ? 1 : 0);

            WinApis.RtlMoveMemory(mxcd.paDetails, ref mxcdb, CoreApis.SizeOf(mxcdb));

            MMSYSERR Rc = WinApis.mixerSetControlDetails(hmixer, ref mxcd, SetControlDetailsFlag.VALUE);
            WinApis.GlobalFree(hmem);

            if (Rc != MMSYSERR.NOERROR)
            {
                return false;
            }

            WinApis.mixerClose(hmixer);

            return true;
        }

        /*
         * Sliders
         * 
         * Slider
         * Has a range of  - 32,768 through 32,767. The mixer driver defines the limits of this control.
         * 
         * Pan
         * Has a range of -32,768 through 32,767. The mixer driver defines the limits of this control, 
         * with 0 as the midrange value.
         * 
         * QSound Pan
         * Provides expanded sound control through QSound. This control has a range of -15 through 15.
         * 
         */

        private static double? GetSliderControlData(
        ComponentType componentType,
        ControlType ctrlType)
        {
            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();

            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return null;
            }

            MIXERCONTROLDETAILS_SIGNED mxcdb = new MIXERCONTROLDETAILS_SIGNED();
            uint mxcd_len = CoreApis.SizeOf(mxcdb);

            hmem = WinApis.GlobalAlloc(64, mxcd_len);
            mxcd.cbDetails = mxcd_len;
            mxcd.paDetails = WinApis.GlobalLock(hmem);

            MMSYSERR Rc = WinApis.mixerGetControlDetails(hmixer, ref mxcd, GetControlDetailsFlag.VALUE);
            if (Rc != MMSYSERR.NOERROR)
            {
                WinApis.GlobalFree(hmem);
                return null;
            }

            WinApis.RtlMoveMemory(ref mxcdb, mxcd.paDetails, mxcd_len);
            WinApis.GlobalFree(hmem);

            WinApis.mixerClose(hmixer);

            if (mxcdb.lValue > 0)
            {
                return (double)mxcdb.lValue / mxc.dwMaximum;
            }
            else
            {
                return -(double)mxcdb.lValue / mxc.dwMinimum;
            }
        }

        private static bool SetSliderControlData(
            ComponentType componentType,
            ControlType ctrlType,
            double value)
        {
            uint hmixer = 0;
            uint hmem = 0;
            MIXERCONTROL mxc = new MIXERCONTROL();
            MIXERCONTROLDETAILS mxcd = new MIXERCONTROLDETAILS();

            if (PrepareControlDetails(componentType, ctrlType, ref hmixer, ref hmem, ref mxc, ref mxcd) ==
                false)
            {
                return false;
            }

            MIXERCONTROLDETAILS_SIGNED mxcdb = new MIXERCONTROLDETAILS_SIGNED();
            hmem = WinApis.GlobalAlloc(64, CoreApis.SizeOf(mxcdb));
            mxcd.cbDetails = CoreApis.SizeOf(mxcdb);
            mxcd.paDetails = WinApis.GlobalLock(hmem);
            if (value >= 0)
            {
                mxcdb.lValue = (long)(value * mxc.dwMaximum);
            }
            else
            {
                mxcdb.lValue = (long)(-value * mxc.dwMinimum);
            }

            WinApis.RtlMoveMemory(mxcd.paDetails, ref mxcdb, CoreApis.SizeOf(mxcdb));

            MMSYSERR Rc = WinApis.mixerSetControlDetails(hmixer, ref mxcd, SetControlDetailsFlag.VALUE);
            WinApis.GlobalFree(hmem);

            if (Rc != MMSYSERR.NOERROR)
            {
                return false;
            }

            WinApis.mixerClose(hmixer);

            return true;
        }
    }
}
